package org.flexthinker.module.impl
{
    import flash.system.ApplicationDomain;
    import flash.system.SecurityDomain;
    import flash.utils.ByteArray;
    import flash.utils.Dictionary;
    
    import mx.core.IFlexModuleFactory;
    import mx.events.ModuleEvent;
    import mx.logging.ILogger;
    import mx.logging.Log;
    import mx.modules.IModuleInfo;
    import mx.utils.StringUtil;
    
    import org.flexthinker.module.Descriptor;
    import org.flexthinker.module.DescriptorLoader;
    import org.flexthinker.module.DescriptorLoaderFactory;

    /**
     * Loads multiple descriptors at once and reports the overall progress
     */
    public class CompositeDescriptorLoader extends DescriptorLoaderImpl
    {
        private var log:ILogger = Log.getLogger("org.flexthinker.module.impl.CompositeDescriptorLoader");

        protected var loaderFactory:DescriptorLoaderFactory;

        protected var moduleLoaders:Dictionary = new Dictionary(true);

        private var descriptorsLoaders:Vector.<DescriptorLoader>;

        public function CompositeDescriptorLoader(descriptor:Descriptor, loaderFactory:DescriptorLoaderFactory = null)
        {
            super(descriptor);

            if (loaderFactory == null)
            {
                loaderFactory = new DescriptorLoaderFactoryImpl();
            }

            this.loaderFactory = loaderFactory;
        }

        public function get numDescriptors():int
        {
            return getDescriptors().length;
        }

        override public function load(applicationDomain:ApplicationDomain = null, securityDomain:SecurityDomain = null,
                bytes:ByteArray = null, moduleFactory:IFlexModuleFactory = null):void
        {
            if (applicationDomain || securityDomain || bytes )
            {
                throw new Error("To control load parameters use instead the ModuleDescriptor.");
            }
			
			this.moduleFactory = moduleFactory;

            // TODO: if is loading in progress stop loading first
            // TODO: if it has been loaded, unload first
            // TODO: remove all keys within moduleLoaders
            moduleLoaders = new Dictionary(true);

            loadDescriptors();
        }


        // ------------
        //  internals
        // ------------

        /**
         * To be overridden by the super class.
		 * Currently ModuleDescriptorLoader and CompositeModuleDescriptorLoader are overridin this method.
         * @return
         *      A Vector with Descriptors to be loaded.
         */
        protected function getDescriptors():Vector.<Descriptor>
        {
            return null;

        }

        protected function moduleReady(event:ModuleEvent):void
        {
            dispatchEvent(new ModuleEvent(ModuleEvent.READY, false, false, event.bytesLoaded, event.bytesTotal,
                    null, this));
        }

        /**
         * A list with the corresponding loaders of each descriptor.
         * @return Vector list with <code>DescriptorLoader</code> objects
         */
        protected function getDescriptorLoaders():Vector.<DescriptorLoader>
        {
            if (!descriptorsLoaders)
            {
                descriptorsLoaders = new Vector.<DescriptorLoader>();
                for each (var descr:Descriptor in getDescriptors())
                {
                    descriptorsLoaders.push(this.loaderFactory.getLoader(descr));
                }
            }

            return descriptorsLoaders;
        }

        private function loadDescriptors():void
        {
            for each (var loader:DescriptorLoader in getDescriptorLoaders())
            {
                loadDescriptor(loader);
            }
        }

        private function loadDescriptor(loader:DescriptorLoader):void
        {
            CONFIG::DEBUG
            {
                log.debug(StringUtil.substitute("Loading [{0}], using loader {1}", loader.url, loader));
            }

            addLoaderListeners(loader);

            loader.load(null,null,null,moduleFactory);
        }

        private function addLoaderListeners(loader:DescriptorLoader):void
        {
            loader.addEventListener(ModuleEvent.PROGRESS, progressHandler);// , false, 0, true );
            loader.addEventListener(ModuleEvent.READY, readyHandler); //, false, 0, true);
            loader.addEventListener(ModuleEvent.ERROR, errorHandler); //, false, 0, true);
            loader.addEventListener(ModuleEvent.UNLOAD, unloadHandler); //, false, 0, true);
            //            loader.addEventListener(ModuleEvent.SETUP, redispatchModuleEvent, false, 0, true);
        }

        private function removeLoaderListeners(loader:IModuleInfo):void
        {
            loader.removeEventListener(ModuleEvent.PROGRESS, progressHandler);
            loader.removeEventListener(ModuleEvent.READY, readyHandler);
            loader.removeEventListener(ModuleEvent.ERROR, errorHandler);
            //   THIS IS REMOVED ONLY AFTER UNLOAD - > loader.removeEventListener(ModuleEvent.UNLOAD, unloadHandler);
            //            loader.removeEventListener(ModuleEvent.SETUP, redispatchModuleEvent);
        }

        private function progressHandler(event:ModuleEvent):void
        {
            var info:IModuleInfo = IModuleInfo(event.module);

            moduleLoaders[ info ] = event;

            var loaded:Number = 0;
            var total:Number = 0;
            for each (var evt:ModuleEvent in moduleLoaders)
            {
                if (!evt)
                {
                    continue;
                }
                loaded += evt.bytesLoaded;
                total += evt.bytesTotal;
            }
            dispatchEvent(new ModuleEvent(event.type, false, false, loaded, total, null, this)); // it was "info" instead of "this"
        }

        private function readyHandler(event:ModuleEvent):void
        {
            var info:IModuleInfo = IModuleInfo(event.module);

            var lastEvent:ModuleEvent = moduleLoaders[ info ] as ModuleEvent;

            if (lastEvent && lastEvent.type == ModuleEvent.PROGRESS)
            {
                event.bytesLoaded = moduleLoaders[ info ].bytesLoaded;
                event.bytesTotal = moduleLoaders[ info ].bytesTotal;
            }

            CONFIG::DEBUG
            {
                log.debug(StringUtil.substitute("[{0}] is ready ... bytesTotal/bytesLoaded={1}/{2}", event.module.url,
                        event.bytesTotal, event.bytesLoaded));
            }

            moduleLoaders[ info ] = event;

            removeLoaderListeners(event.module);

            var allLoaded:Boolean = true;
            var readyCount:int = 0; // counts how many modules are ready
            for each (var evt:ModuleEvent in moduleLoaders)
            {
                if (!evt)
                {
                    continue;
                }
                if (evt.type == ModuleEvent.READY)
                {
                    readyCount ++;
                }
            }

            allLoaded = ( readyCount == numDescriptors );

            CONFIG::DEBUG
            {
                log.debug(StringUtil.substitute("{0} out of {1} descriptors are ready for ( {2},{3} )", readyCount,
                        numDescriptors, this.descriptor.name, this.descriptor.url));
            }
            if (allLoaded)
            {
                //dispatchEvent( event.clone() );
                moduleReady(event);
            }
        }

        private function errorHandler(event:ModuleEvent):void
        {
            CONFIG::DEBUG
            {
                log.debug(StringUtil.substitute("Error loading descriptor. ErrorText:{0}", event.errorText));
            }
            dispatchEvent(event.clone());
        }

        private function unloadHandler(event:ModuleEvent):void
        {
            removeLoaderListeners(event.module);
            moduleLoaders[ event.module ] = null;
            event.module.removeEventListener(ModuleEvent.UNLOAD, unloadHandler);
        }

        override protected function moduleUnloaded():void
        {
            super.moduleUnloaded();

            unloadAll(getDescriptorLoaders());

            descriptorsLoaders = null;
        }

        private function unloadAll(loaders:Vector.<DescriptorLoader>):void
        {
            for each (var loader:DescriptorLoader in loaders)
            {
                CONFIG::DEBUG
                {
                    log.debug("Unloading {0} ( ready={1} ) ... in  {2} ", loader.url, loader.ready, this);
                }
//                loader.addEventListener(ModuleEvent.UNLOAD, loader_unloadHandler);
                loader.unload();
            }
        }

        /*
         private function loader_unloadHandler(event:ModuleEvent):void
         {
         CONFIG::DEBUG
         {
         log.debug("{0} has been UNLOADED !", IModuleInfo(event.currentTarget).url );
         }

         event.currentTarget.removeEventListener(ModuleEvent.UNLOAD, loader_unloadHandler);

         delete moduleLoaders[ IModuleInfo(event.currentTarget) ];
         }
         */

    }
}